from pathlib import Path
import sqlite3
from typing import Iterable, Mapping

from flask import Flask, flash, g, redirect, render_template, request, url_for

# 创建Flask应用实例
app = Flask(__name__)
# 设置密钥，用于会话安全和flash消息
app.config["SECRET_KEY"] = "dev-secret-key"

# 获取项目根目录路径
BASE_DIR = Path(app.root_path)
# 设置SQLite数据库文件路径
DATABASE_PATH = BASE_DIR / "todo.db"


def get_db() -> sqlite3.Connection:
    """
    获取数据库连接
    功能：为每个请求创建或获取数据库连接，确保线程安全
    说明：使用Flask的g对象存储数据库连接，同一个请求中多次调用会返回相同的连接
    """
    if "db" not in g:
        # 连接到SQLite数据库
        g.db = sqlite3.connect(DATABASE_PATH)
        # 设置行工厂，让查询结果以字典形式返回
        g.db.row_factory = sqlite3.Row
    return g.db


def init_db() -> None:
    """
    初始化数据库
    功能：创建数据库文件和todos表（如果不存在）
    说明：应用启动时调用，确保数据库结构正确
    """
    # 确保数据库文件所在目录存在
    DATABASE_PATH.parent.mkdir(parents=True, exist_ok=True)
    conn = sqlite3.connect(DATABASE_PATH)
    # 创建todos表，包含id、标题、完成状态和创建时间字段
    conn.execute(
        """
        CREATE TABLE IF NOT EXISTS todos (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            title TEXT NOT NULL,
            completed INTEGER NOT NULL DEFAULT 0,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
        """
    )
    conn.commit()
    conn.close()


@app.teardown_appcontext
def close_db(_exception) -> None:
    """
    关闭数据库连接
    功能：在请求结束时自动关闭数据库连接
    说明：Flask的teardown_appcontext装饰器确保无论请求成功与否都会执行
    """
    db = g.pop("db", None)
    if db is not None:
        db.close()


@app.route("/", methods=["GET"])
def index():
    """
    首页路由
    功能：显示所有待办事项
    前端效果：访问首页时，从数据库获取所有事项并渲染到HTML模板
    """
    # 从数据库获取所有待办事项
    todos = fetch_todos()
    # 渲染index.html模板，并传入待办事项数据
    return render_template("index.html", todos=todos)


def fetch_todos() -> Iterable[Mapping]:
    """
    获取所有待办事项
    功能：从数据库查询所有待办事项，按完成状态和创建时间排序
    返回：待办事项列表，每个事项包含id、title、completed、created_at字段
    """
    db = get_db()
    # 查询语句：先按completed排序（未完成的在前），再按创建时间降序
    cursor = db.execute(
        "SELECT id, title, completed, created_at FROM todos ORDER BY completed, created_at DESC"
    )
    return cursor.fetchall()


@app.route("/add", methods=["POST"])
def add_todo():
    """
    添加待办事项
    功能：接收表单数据，添加新的待办事项到数据库
    前端效果：提交表单后页面刷新，显示新添加的事项
    """
    # 获取表单中的标题字段并去除首尾空格
    title = request.form.get("title", "").strip()

    # 验证标题不能为空
    if not title:
        flash("请输入有效的事项内容。", "error")
        return redirect(url_for("index"))

    # 获取数据库连接并执行插入操作
    db = get_db()
    db.execute("INSERT INTO todos (title) VALUES (?)", (title,))
    db.commit()
    flash("事项添加成功。", "success")
    # 重定向到首页，页面刷新显示最新数据
    return redirect(url_for("index"))


@app.route("/toggle/<int:todo_id>", methods=["POST"])
def toggle_todo(todo_id: int):
    """
    切换待办事项完成状态
    功能：将指定ID的事项在完成/未完成状态间切换
    前端效果：点击切换按钮后页面刷新，显示更新后的状态
    """
    db = get_db()
    # 查询当前事项的完成状态
    todo = db.execute("SELECT completed FROM todos WHERE id = ?", (todo_id,)).fetchone()
    if todo is None:
        flash("未找到该事项。", "error")
        return redirect(url_for("index"))

    # 切换完成状态：如果当前是1（完成）则变为0（未完成），反之亦然
    new_status = 0 if todo["completed"] else 1
    db.execute("UPDATE todos SET completed = ? WHERE id = ?", (new_status, todo_id))
    db.commit()
    # 重定向到首页，页面刷新显示更新后的状态
    return redirect(url_for("index"))


@app.route("/edit/<int:todo_id>", methods=["POST"])
def edit_todo(todo_id: int):
    """
    编辑待办事项
    功能：修改指定ID的事项标题
    前端效果：提交编辑后页面刷新，显示更新后的标题
    """
    # 获取表单中的新标题
    new_title = request.form.get("title", "").strip()
    if not new_title:
        flash("事项内容不能为空。", "error")
        return redirect(url_for("index"))

    db = get_db()
    # 执行更新操作
    updated = db.execute(
        "UPDATE todos SET title = ? WHERE id = ?", (new_title, todo_id)
    )
    # 检查是否成功更新了记录
    if updated.rowcount == 0:
        flash("未找到该事项。", "error")
        return redirect(url_for("index"))

    db.commit()
    flash("事项更新成功。", "success")
    # 重定向到首页，页面刷新显示更新后的内容
    return redirect(url_for("index"))


@app.route("/delete/<int:todo_id>", methods=["POST"])
def delete_todo(todo_id: int):
    """
    删除待办事项
    功能：从数据库中删除指定ID的事项
    前端效果：删除后页面刷新，该事项从列表中消失
    """
    db = get_db()
    # 执行删除操作
    deleted = db.execute("DELETE FROM todos WHERE id = ?", (todo_id,))
    # 检查是否成功删除了记录
    if deleted.rowcount == 0:
        flash("未找到该事项。", "error")
        return redirect(url_for("index"))

    db.commit()
    flash("事项已删除。", "success")
    # 重定向到首页，页面刷新显示删除后的列表
    return redirect(url_for("index"))


# 应用启动时初始化数据库
init_db()

if __name__ == "__main__":
    # 启动Flask开发服务器，debug=True启用调试模式
    app.run(debug=True)